////////////////////////////////////////////////////////////////////////////////////////////////////
// file:	Packer.cs
//
// summary:	Packer.cs is an optional file to programmatically extend and customize how NuGet 
// packages are created. If deleted, the default behavior is used. Packer.cs inherits 
// ReferencedProjectPacker which handles how packages are created by default. Packer.cs only needs 
// to implement INuGetPackagingHandler  and does not have to inherit ReferencedProjectPacker. 
// Returning null or empty values results in the default behavior. PLEASE KEEP IN MIND THAT ONCE 
// THIS FILE IS MODIFIED NUGET WILL NOT UPDATE THE FILE. AFTER EACH UPDATE THE FILE 
// PACKER.CS.SOURCE.TXT WILL BE REPLACED WITHE THE LATEST VERSION OF PACKER.CS. DO NOT MODIFY 
// PACKER.CS.SOURCE.TXT.
////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using NuGet;
using Adapt.Package.NuGets.Projects.Build.Manifests;
using Adapt.Package.NuGets.Projects.Build.Repositories;
using Adapt.Package.NuGets.Projects.Build.Libraries;
using Adapt.Package.NuGets.Projects.Build.Template;
using Adapt.Package.NuGets.Projects.Build.Packaging;
using Adapt.Package.NuGets.Projects.Build.Tasks;

namespace NuGet.Package
{
    ////////////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>   Extends and customizes how NuGet packages are created</summary>
    ///
    /// <remarks>   </remarks>
    ////////////////////////////////////////////////////////////////////////////////////////////////////

    public class Packer : Adapt.Package.NuGets.Projects.Build.Tasks.ReferencedProjectPacker //Adapt.Package.NuGets.Projects.Build.Packaging.INuGetPackagingHandler
    {
        #region Private Variables

        #endregion

        #region Constructors

        #endregion

        #region Constant Variables and Enumerations

        #endregion

        #region Variable Containers
        protected DateTime UtcStartTime { get; set; }
        #endregion

        #region System Event Handlers

        #endregion

        #region Custom Event Handlers

        #endregion

        #region Overridden Event Handlers

        #endregion

        #region Public Methods
        
        #endregion

        #region Private Methods

        #endregion

        #region Overridden Methods

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Start. </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="HandlerContext">   Context for tha packaging process. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override void Start(INuGetPackagingHandlerContext HandlerContext)
        {
            this.UtcStartTime = DateTime.UtcNow;
            HandlerContext.Log.LogMessage("Start " + this.GetType().FullName + " (" + this.UtcStartTime.ToString() + ")");
            base.Start(HandlerContext);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Load the template package manifest file, by default "PackageManifest.nuspec.xml" </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="HandlerContext">   Context for tha packaging process. </param>
        ///
        /// <returns>   The package manifest file. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override FileInfo GetPackageManifestFile(INuGetPackagingHandlerContext HandlerContext)
        {
            HandlerContext.Log.LogMessage("GetPackageManifestFile");
            return base.GetPackageManifestFile(HandlerContext);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Gets directory in the current packaging project where a copy of the Nuget package
        ///             should be stored. By default "bin\.nuget\". </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="HandlerContext">   Context for tha packaging process. </param>
        ///
        /// <returns>   The project respository directory. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override DirectoryInfo GetProjectRespositoryDirectory(INuGetPackagingHandlerContext HandlerContext)
        {
            HandlerContext.Log.LogMessage("GetProjectRespositoryDirectory");
            return base.GetProjectRespositoryDirectory(HandlerContext);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Load respositories where NuGet packages should be output to. </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="OutputRespositories">  [in,out] The output respositories. </param>
        /// <param name="HandlerContext">       Context for tha packaging process. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override void PostLoadOutputRespositories(ref List<INuGetPackageRepository> OutputRespositories, INuGetPackagingHandlerContext HandlerContext)
        {
            HandlerContext.Log.LogMessage("PostLoadOutputRespositories");
            base.PostLoadOutputRespositories(ref OutputRespositories, HandlerContext);

            /*
            //Publishing to Server (Web Feed) Option 1
            //Method: Add a repository which will publish the package as long as "bool PublishPackage" is set to true in "PrePublish"
            Uri Endpoint = new Uri("");
            string APIKey = "";
            System.Net.NetworkCredential Credentials = new System.Net.NetworkCredential("username", "password");
            string RepositoryName = "AName"; //Name is optional and can be used in "PostPackage" to find this repository in "IEnumerable<IRepositoryPublishingResult> PublishingResults".
            ServerNuGetPackageRepository WebRepository = new ServerNuGetPackageRepository(RepositoryName, Endpoint, APIKey, Credentials);
            OutputRespositories.Add(WebRepository);
            */   
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Parse semantic version loaded from template package manifest file, and replace any
        ///             placeholders such as "BUILD" or "REVISION". </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="CurrentUnparsedRevision">  The current unparsed revision. </param>
        /// <param name="CurrentParsedRevision">    [out] The current parsed revision. </param>
        /// <param name="HandlerContext">           Context for tha packaging process. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override void ParseSemanticVersion(string CurrentUnparsedRevision, out string CurrentParsedRevision, INuGetPackagingHandlerContext HandlerContext)
        {
            HandlerContext.Log.LogMessage("ParseSemanticVersion");
            base.ParseSemanticVersion(CurrentUnparsedRevision, out CurrentParsedRevision, HandlerContext);

            /*
            //The version in PackageManifest.nuspec.xml may use variables that are replaced with values such as a build number.
            //By default, the version in PackageManifest.nuspec.xml is "0.0.BUILD.REVISION". “BUILD” is automatically replaced with 
            //“yyyyMMdd” (such as 20120826) and “REVISION” is replaced with the total seconds that have elapsed for the day.
            string VersionBuildNumberPlaceholder = "BUILD";
            string VersionBuildRevisionNumberPlaceholder = "REVISION";
            
            DateTime UtcBuildDateTime = DateTime.UtcNow;
            string BuildNumber = UtcBuildDateTime.ToString("yyyyMMdd");
            TimeSpan Seconds = UtcBuildDateTime - new DateTime(UtcBuildDateTime.Year, UtcBuildDateTime.Month, UtcBuildDateTime.Day, 0, 0, 0, DateTimeKind.Utc);
            string RevisionNumber = System.Math.Round(Seconds.TotalSeconds, 0, MidpointRounding.ToEven).ToString();

            string ParsedVersionNumber = CurrentUnparsedRevision;
            ParsedVersionNumber = ParsedVersionNumber.Replace(VersionBuildNumberPlaceholder, BuildNumber);
            ParsedVersionNumber = ParsedVersionNumber.Replace(VersionBuildRevisionNumberPlaceholder, RevisionNumber);
            CurrentParsedRevision = ParsedVersionNumber;
            */
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Increment semantic version for NuGet package being created. </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="CurrentParsedRevision">    [out] The current parsed revision. </param>
        /// <param name="IncrementedRevision">      [out] The incremented revision. </param>
        /// <param name="HandlerContext">           Context for tha packaging process. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override void IncrementSemanticVersion(NuGet.SemanticVersion CurrentParsedRevision, out NuGet.SemanticVersion IncrementedRevision, INuGetPackagingHandlerContext HandlerContext)
        {
            HandlerContext.Log.LogMessage("IncrementSemanticVersion");
            base.IncrementSemanticVersion(CurrentParsedRevision, out IncrementedRevision, HandlerContext);

            /*
            //Programmatically change the major and minor revision of the version found in PackageManifest.nuspec.xml. 
            IncrementedRevision = new NuGet.SemanticVersion(CurrentParsedRevision.Version.Major + 1, CurrentParsedRevision.Version.Minor + 1, 0, 0);
            */
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Add, modify, or remove files and dependencies to be included in the NuGet package.  </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="ManifestBuilder">  [in,out] The manifest builder. </param>
        /// <param name="HandlerContext">   Context for tha packaging process. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override void PrePackage(ref NuGetPackageManifestBuilder ManifestBuilder, INuGetPackagingHandlerContext HandlerContext)
        {
            //Add assemblies, files, etc. to package manifest 
            //Note: Files could even be downloaded and added from remote sources such Azure or FTP

            HandlerContext.Log.LogMessage("PrePackage");
            base.PrePackage(ref ManifestBuilder, HandlerContext);

            /*
            //Option 1. Add files to manifest using NuGet.Core
            //Example: Add "content file" to Content\Folder1\file.xml in the package which will be added in the target project to Folder1\file.xml
            System.IO.FileInfo SourceFile = new System.IO.FileInfo(@"c:\folder\folder\file.xml");
            if (SourceFile.Exists == true)
            {
                string NuGetPackageContentFolderName = "Content";
                string NuGetPackageShortFrameworkName = "net40";
                string ContentPath = "Folder1";
                NuGet.ManifestFile ContentFile = new ManifestFile();
                ContentFile.Source = SourceFile.FullName;
                ContentFile.Target = System.IO.Path.Combine(NuGetPackageContentFolderName, NuGetPackageShortFrameworkName + "\\", ContentPath);
                ManifestBuilder.PackageManifest.Files.Add(ContentFile);
            }
            */

            /*
            //Option 2. Add files to manifest using Adapt.Package.NuGets.Projects.Build.Manifests.NuGetPackageManifestBuilder
            //Example: Add "content file" to Content\Folder1\file.xml in the package which will be added in the target project to Folder1\file.xml
            System.IO.FileInfo SourceFile = new System.IO.FileInfo(@"c:\folder\folder\file.xml");
            string NuGetPackageShortFrameworkName = "net40"; 
            string ContentPath = "Folder1";
            ManifestBuilder.AddContentFile(SourceFile, NuGetPackageContentTargets.Content, NuGetPackageShortFrameworkName, ContentPath);
            
            //Helpful extensions:
            //string NuGetPackageContentFolderName = NuGetPackageContentTargets.Src.GetNuGetPackageContentFolderName();
            //Microsoft.Build.Evaluation.Project SolutionProject = HandlerContext.CurrentProject;
            //string BuildConfiguration = SolutionProject.GetBuildConfiguration(); //i.e. debug
            //string AssemblyName = SolutionProject.GetAssemblyName();
            //Note: GetTargetFrameworkNames() will return multiple names for portable library project
            //NuGetPackageShortFrameworkName = SolutionProject.GetTargetFrameworkNames().First().GetNuGetPackageShortFrameworkName();
            */

            /*
            //Option 3. Replace automatic adding of files to manifest
            //Note: MAKE SURE TO COMMENT OUT "base.PrePackage(ref ManifestBuilder, HandlerContext);" to disable defualt behaviour

            //base.PrePackage(ref ManifestBuilder, HandlerContext);

            //Basic example:
            foreach (Microsoft.Build.Evaluation.Project SolutionProject in HandlerContext.IncludedProjects)
            {
                foreach (System.Runtime.Versioning.FrameworkName TargetFrameworkName in SolutionProject.GetTargetFrameworkNames())
                {
                    string NuGetPackageShortFrameworkName = TargetFrameworkName.GetNuGetPackageShortFrameworkName();
                    //Copy assembly from project 
                    ManifestBuilder.AddContentFile(new System.IO.FileInfo(System.IO.Path.Combine(SolutionProject.DirectoryPath, "bin/" + SolutionProject.GetBuildConfiguration() + "/" + SolutionProject.GetAssemblyName() + ".dll")), NuGetPackageContentTargets.Lib, TargetFrameworkName.GetNuGetPackageShortFrameworkName(), "");
                    //Add Framework Assemblies
                    foreach (string GACAssemblyName in SolutionProject.GetReferencedGACAssemblyNames())
                    {
                       ManifestBuilder.AddFrameworkAssembly(GACAssemblyName, TargetFrameworkName.GetNuGetPackageShortFrameworkName());
                    }
                }
                //Add Dependent NuGet Packages listed in packages/config
                ManifestBuilder.AddNuGetPackageDependency(SolutionProject.GetNuGetPackageReferenceFile());                
            }
            */          
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Confirm NuGet package should be output to the repository. </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="Repository">       The repository. </param>
        /// <param name="PackageManifest">  [in,out] A list of packages. </param>
        /// <param name="PublishPackage">   [out] The publish package. </param>
        /// <param name="HandlerContext">   Context for tha packaging process. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override void PrePublish(INuGetPackageRepository Repository, ref NuGet.Manifest PackageManifest, out bool PublishPackage, INuGetPackagingHandlerContext HandlerContext)
        {
            HandlerContext.Log.LogMessage("PrePublish");  
            base.PrePublish(Repository, ref PackageManifest, out PublishPackage, HandlerContext);

            /*
            //Confirm package should be published
            PublishPackage = false;
            try
            {
                if (Repository.RepositoryType == RepositoryType.Server) //|| Repository.RepositoryType == RepositoryType.Local) //Repository.RepositoryName == "AName")
                {
                    //Only publish if Major.Minor version is greater than existing versions. Ignor existing Build and Revision.
                    IEnumerable<NuGet.SemanticVersion> ExistingVersions = Repository.GetPublishedVersions(PackageManifest.Metadata.Id);
                    NuGet.SemanticVersion VersionToPublish = new NuGet.SemanticVersion(PackageManifest.Metadata.Version);
                    double VersionToPublishMajorMinor = Convert.ToDouble(VersionToPublish.Version.Major + "." + VersionToPublish.Version.Minor);
                    double MaximumExistingVersionMajorMinor = ExistingVersions.Select(x => Convert.ToDouble(x.Version.Major + "." + x.Version.Minor)).Max();
                    PublishPackage = (VersionToPublishMajorMinor > MaximumExistingVersionMajorMinor);
                }
            }
            catch { }
            */
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Perform tasks after all packages have been output. </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="PackageManifest">      A list of packages. </param>
        /// <param name="PublishingResults">    The publishing results. </param>
        /// <param name="HandlerContext">       Context for tha packaging process. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override void PostPackage(NuGet.Manifest PackageManifest, IEnumerable<IRepositoryPublishingResult> PublishingResults, INuGetPackagingHandlerContext HandlerContext)
        {            
            HandlerContext.Log.LogMessage("PostPackage");
            base.PostPackage(PackageManifest, PublishingResults, HandlerContext);

            /*
            //Publishing to Server (Web Feed) Option 2
            //Method: Upload package to server using "NuGetServerClient"
            Uri Endpoint = new Uri("");
            string APIKey = "";
            System.Net.NetworkCredential Credentials = new System.Net.NetworkCredential("username", "password");
            bool PublishSuccess = NuGetServerClient.Publish(PackageManifest, Endpoint, APIKey, Credentials, new System.Net.CookieContainer());      
            */       
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Unparse the semantic version of the output package and add placeholders such as "BUILD". </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="IncrementedRevision">          [out] The incremented revision. </param>
        /// <param name="UnparsedIncrementedRevision">  [out] The unparsed incremented revision. </param>
        /// <param name="HandlerContext">               Context for tha packaging process. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override void UnparseSemanticVersion(NuGet.SemanticVersion IncrementedRevision, out string UnparsedIncrementedRevision, INuGetPackagingHandlerContext HandlerContext)
        {            
            HandlerContext.Log.LogMessage("UnparseSemanticVersion");
            base.UnparseSemanticVersion(IncrementedRevision, out UnparsedIncrementedRevision, HandlerContext);

            /*
            //The version in PackageManifest.nuspec.xml may use variables that are replaced with values such as a build number.
            //By default, the version in PackageManifest.nuspec.xml is "0.0.BUILD.REVISION". “BUILD” is automatically replaced with 
            //“yyyyMMdd” (such as 20120826) and “REVISION” is replaced with the total seconds that have elapsed for the day.
            string VersionBuildNumberPlaceholder = "BUILD";
            string VersionBuildRevisionNumberPlaceholder = "REVISION";
            UnparsedIncrementedRevision = IncrementedRevision.Version.Major + "." + IncrementedRevision.Version.Minor + "." + VersionBuildNumberPlaceholder + "." + VersionBuildRevisionNumberPlaceholder;
            */
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Perform tasks before the template package manifest is updated. </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="TemplatePackageManifest">          [in,out] A list of template packages. </param>
        /// <param name="UnparsedIncrementedRevision">      [out] The unparsed incremented revision. </param>
        /// <param name="OutputPackageManifest">            A list of output packages. </param>
        /// <param name="UpdateTemplatePackageManifest">    [out] A list of update template packages. </param>
        /// <param name="HandlerContext">                   Context for tha packaging process. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override void PreUpdateTemplatePackageManifest(ref NuGet.Manifest TemplatePackageManifest, string UnparsedIncrementedRevision, NuGet.Manifest OutputPackageManifest, out bool UpdateTemplatePackageManifest, INuGetPackagingHandlerContext HandlerContext)
        {
            HandlerContext.Log.LogMessage("PreUpdateTemplatePackageManifest"); 
            base.PreUpdateTemplatePackageManifest(ref TemplatePackageManifest, UnparsedIncrementedRevision, OutputPackageManifest, out UpdateTemplatePackageManifest, HandlerContext);

            /*
            //Do not update PackageManifest.nuspec.xml
            UpdateTemplatePackageManifest = false;
            */

            /*
            //Update PackageManifest.nuspec.xml
            UpdateTemplatePackageManifest = true;
            TemplatePackageManifest.Metadata.Version = UnparsedIncrementedRevision;
            */
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   End. </summary>
        ///
        /// <remarks>   </remarks>
        ///
        /// <param name="HandlerContext">   Context for tha packaging process. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////

        public override void End(INuGetPackagingHandlerContext HandlerContext)
        {
            TimeSpan PackagingTime = (DateTime.UtcNow - this.UtcStartTime);
            HandlerContext.Log.LogMessage("End " + this.GetType().FullName + " (" + PackagingTime.TotalSeconds + " Seconds)");
            base.End(HandlerContext);
        }
        #endregion
    }
}
